Tutorial : Web Application tutorial
This article introduces the WebApp
development of JSRE
. First explain the basic mechanism of the WebApp
framework, then introduce some applications of built-in middleware. The content is organized as follows:
JSRE
WebApp
base mechanism:Using build-in middleware:
The corresponding project for the example can be found in the 'example' directory of JSRE
.
Route
Routing
refers to how an application’s endpoints (URIs) respond to client requests.
You define routing using methods of the app
object that correspond to HTTP methods; for example, app.get()
to handle GET requests and app.post()
to handle POST requests. For a full list, see app.METHOD. You can also use app.all() to handle all HTTP methods and app.use() to specify middleware as the callback function (See Using middleware for details).
These routing methods specify a callback function (sometimes called “handler functions”) called when the application receives a request to the specified route (endpoint) and HTTP method. In other words, the application “listens” for requests that match the specified route(s) and method(s), and when it detects a match, it calls the specified callback function.
In fact, the routing methods can have more than one callback function as arguments. With multiple callback functions, it is important to provide next
as an argument to the callback function and then call next()
within the body of the function to hand off control to the next callback.
The following code is an example of a very basic route.
var WebApp = require('webapp');
// Create app.
var app = WebApp.create('app', 0, socket.sockaddr(socket.INADDR_ANY, 8000));
// respond with "hello world" when a GET request is made to the homepage
app.get('/', function (req, res) {
res.send('hello world');
});
Route Methods
A route method is derived from one of the HTTP methods, and is attached to an instance of the WebApp
class.
The following code is an example of routes that are defined for the GET and the POST methods to the root of the app.
// GET method route
app.get('/', function (req, res) {
res.send('GET request to the homepage');
});
// POST method route
app.post('/', function (req, res) {
res.send('POST request to the homepage');
});
WebApp
supports methods that correspond to all HTTP request methods: get
, post
, and so on. For a full list, see app.METHOD.
There is a special routing method, app.all()
, used to load middleware functions at a path for all HTTP request methods. For example, the following handler is executed for requests to the route “/secret” whether using GET, POST, PUT, DELETE, or any other HTTP request method.
app.all('/secret', function (req, res, next) {
console.log('Accessing the secret section ...');
next(); // pass control to the next handler
});
Route Paths
Route paths, in combination with a request method, define the endpoints at which requests can be made. Route paths can be strings, string patterns, or regular expressions.
The characters ?
, +
, *
, and ()
are subsets of their regular expression counterparts. The hyphen (-
) and the dot (.
) are interpreted literally by string-based paths.
If you need to use the dollar character ($
) in a path string, enclose it escaped within ([
and ])
. For example, the path string for requests at “/data/$book
”, would be “/data/([\$])book
”.
Query strings are not part of the route path.
Here are some examples of route paths based on strings.
This route path will match requests to the root route, /
.
app.get('/', function (req, res) {
res.send('root');
});
This route path will match requests to /about
.
app.get('/about', function (req, res) {
res.send('about');
});
This route path will match requests to /random.text
.
app.get('/random.text', function (req, res) {
res.send('random.text');
});
Here are some examples of route paths based on string patterns.
This route path will match acd
and abcd
.
app.get('/ab?cd', function (req, res) {
res.send('ab?cd');
});
This route path will match abcd
, abbcd
, abbbcd
, and so on.
app.get('/ab+cd', function (req, res) {
res.send('ab+cd');
});
This route path will match abcd
, abxcd
, abRANDOMcd
, ab123cd
, and so on.
app.get('/ab*cd', function (req, res) {
res.send('ab*cd');
});
This route path will match /abe
and /abcde
.
app.get('/ab(cd)?e', function (req, res) {
res.send('ab(cd)?e');
});
Examples of route paths based on regular expressions:
This route path will match anything with an “a” in it.
app.get(/a/, function (req, res) {
res.send('/a/');
});
This route path will match butterfly
and dragonfly
, but not butterflyman
, dragonflyman
, and so on.
app.get(/.*fly$/, function (req, res) {
res.send('/.*fly$/');
});
Route Parameters
Route parameters are named URL segments that are used to capture the values specified at their position in the URL. The captured values are populated in the req.params
object, with the name of the route parameter specified in the path as their respective keys.
Route path: /users/:userId/books/:bookId
Request URL: http://<192.168.7.32>:8000/users/34/books/8989
req.params: { "userId": "34", "bookId": "8989" }
To define routes with route parameters, simply specify the route parameters in the path of the route as shown below.
app.get('/users/:userId/books/:bookId', function (req, res) {
res.send(req.params);
});
The name of route parameters must be made up of “word characters” ([A-Za-z0-9_]).
Since the hyphen (-
) and the dot (.
) are interpreted literally, they can be used along with route parameters for useful purposes.
Route path: /flights/:from-:to
Request URL: http://<192.168.7.32>:8000/flights/LAX-SFO
req.params: { "from": "LAX", "to": "SFO" }
Route path: /plantae/:genus.:species
Request URL: http://<192.168.7.32>:8000/plantae/Prunus.persica
req.params: { "genus": "Prunus", "species": "persica" }
To have more control over the exact string that can be matched by a route parameter, you can append a regular expression in parentheses (()
):
Route path: /user/:userId(\d+)
Request URL: http://<192.168.7.32>:8000/user/42
req.params: {"userId": "42"}
Because the regular expression is usually part of a literal string, be sure to escape any \
characters with an additional backslash, for example \\d+
.
Route Handlers
You can provide multiple callback functions.
Route handlers can be in the form of a function, or an array of functions as shown in the following examples.
A single callback function can handle a route. For example:
app.get('/example/a', function (req, res) {
res.send('Hello from A!');
});
More than one callback function can handle a route (make sure you specify the next
object). For example:
app.get('/example/b', function (req, res, next) {
console.log('the response will be sent by the next function ...');
next();
}, function (req, res) {
res.send('Hello from B!');
});
Response Methods
The methods on the response object (res
) in the following table can send a response to the client, and terminate the request-response cycle. If none of these methods are called from a route handler, the client request will be left hanging.
Method | Description |
---|---|
res.end() | End the response process. |
res.json() | Send a JSON response. |
res.redirect() | Redirect a request. |
res.render() | Render a view template. |
res.send() | Send a response of various types. |
res.sendFile() | Send a file as an octet stream. |
res.sendStatus() | Set the response status code and send its string representation as the response body. |
app.route()
You can create chainable route handlers for a route path by using app.route()
. Because the path is specified at a single location, creating modular routes is helpful, as is reducing redundancy and typos. For more information about routes, see: Router() documentation.
Here is an example of chained route handlers that are defined by using app.route()
.
app.route('/book')
.get(function (req, res) {
res.send('Get a random book');
})
.post(function (req, res) {
res.send('Add a book');
})
.put(function (req, res) {
res.send('Update the book');
});
Router
Use the Router
class to create modular, mountable route handlers. A Router
instance is a complete middleware and routing system; for this reason, it is often referred to as a “mini-app”.
The following example creates a router as a module, loads a middleware function in it, defines some routes, and mounts the router module on a path in the main app.
Create a router file named birds.js
in the app directory, with the following content:
var Router = require('webapp').Router;
var router = Router.create();
// middleware that is specific to this router
router.use(function timeLog(req, res, next) {
console.log('Time: ', Date.now());
next();
});
// define the home page route
router.get('/', function (req, res) {
res.send('Birds home page');
});
// define the about route
router.get('/about', function (req, res) {
res.send('About birds');
});
module.exports = router;
Then, load the router module in the app:
var birds = require('./birds');
// ...
app.use('/birds', birds);
The app will now be able to handle requests to /birds
and /birds/about
, as well as call the timeLog
middleware function that is specific to the route.
Middleware
WebApp
is a routing and middleware web framework that has minimal functionality of its own: An application is essentially a series of middleware function calls.
Middleware functions are functions that have access to the request object (req
), the response object (res
), and the next middleware function in the application’s request-response cycle. The next middleware function is commonly denoted by a variable named next
.
Middleware functions can perform the following tasks:
- Execute any code.
- Make changes to the request and the response objects.
- End the request-response cycle.
- Call the next middleware function in the stack.
If the current middleware function does not end the request-response cycle, it must call next()
to pass control to the next middleware function. Otherwise, the request will be left hanging.
An application can use the following types of middleware:
- Application-level middleware
- Router-level middleware
- Error-handling middleware
- Built-in middleware
- Third-party middleware
You can load application-level and router-level middleware with an optional mount path. You can also load a series of middleware functions together, which creates a sub-stack of the middleware system at a mount point.
Application-Level Middleware
Bind application-level middleware to an instance of the app
object by using the app.use()
and app.METHOD()
functions, where METHOD
is the HTTP method of the request that the middleware function handles (such as GET, PUT, or POST) in lowercase.
This example shows a middleware function with no mount path. The function is executed every time the app receives a request.
app.use(function (req, res, next) {
console.log('Time:', Date.now());
next();
});
This example shows a middleware function mounted on the /user/:id
path. The function is executed for any type of HTTP request on the /user/:id
path.
app.use('/user/:id', function (req, res, next) {
console.log('Request Type:', req.method);
next();
});
This example shows a route and its handler function (middleware system). The function handles GET requests to the /user/:id
path.
app.get('/user/:id', function (req, res, next) {
res.send('USER');
});
Router-Level Middleware
Router-level middleware works in the same way as application-level middleware, except it is bound to an instance of Router
.
var Router = require('webapp').Router;
var router = Router.create();
Load router-level middleware by using the router.use()
and router.METHOD()
functions.
The following example code replicates the middleware system that is shown above for application-level middleware, by using router-level middleware:
var Router = require('webapp').Router;
var router = Router.create();
// a middleware function with no mount path. This code is executed for every request to the router
router.use(function (req, res, next) {
console.log('Time:', Date.now());
next();
});
// a middleware sub-stack shows request info for any type of HTTP request to the /user/:id path
router.use('/user/:id', function (req, res, next) {
console.log('Request URL:', req.url);
next();
}, function (req, res, next) {
console.log('Request Type:', req.method);
next();
});
// handler for the /user/:id path, which renders a special page
router.get('/user/:id', function (req, res, next) {
console.log(req.params.id);
res.render('special');
});
// mount the router on the app
app.use('/', router);
Error-Handling Middleware
Error-handling middleware always takes four arguments. You must provide four arguments to identify it as an error-handling middleware function. Even if you don’t need to use the next
object, you must specify it to maintain the signature. Otherwise, the next
object will be interpreted as regular middleware and will fail to handle errors.
Define error-handling middleware functions in the same way as other middleware functions, except with four arguments instead of three, specifically with the signature (err, req, res, next)
:
app.use(function (err, req, res, next) {
console.error(err.stack);
res.status(500).send('Something broke!');
});
For details about error-handling middleware, see: Error handling.
Built-In Middleware
Express has the following built-in middleware:
- cookieParser Parse Cookie header and populate
req.cookies
with an object keyed by the cookie names. - queryParser Parse Query strings to objects
req.query
and objects to strings. - serveStatic serves static assets such as HTML files, images, and so on.
- bodyParser Parse incoming request bodies in a middleware before your handlers, available under the
req.body
property. - multer A middleware for handling
multipart/form-data
, which is primarily used for uploading files. - session Use
session
to identify the specific user.
Error Handling
Error Handling refers to how WebApp
catches and processes errors that occur both synchronously and asynchronously. WebApp
comes with a default error handler so you don’t need to write your own to get started.
Catching Errors
It’s important to ensure that WebApp
catches all errors that occur while running route handlers and middleware.
Errors that occur in synchronous code inside route handlers and middleware require no extra work. If synchronous code throws an error, then WebApp
will catch and process it. For example:
app.get('/', function (req, res) {
throw new Error('BROKEN'); // WebApp will catch this on its own.
})
For errors returned from asynchronous functions invoked by route handlers and middleware, you must pass them to the next()
function, where WebApp
will catch and process them.
If you pass anything to the next()
function, WebApp
regards the current request as being an error and will skip any remaining non-error handling routing and middleware functions.
You must catch errors that occur in asynchronous code invoked by route handlers or middleware and pass them to WebApp
for processing. For example:
app.get('/', function (req, res, next) {
setTimeout(function () {
try {
throw new Error('BROKEN');
} catch (err) {
next(err);
}
}, 100);
});
The above example uses a try...catch
block to catch errors in the asynchronous code and pass them to WebApp
. If the try...catch
block were omitted, WebApp
would not catch the error since it is not part of the synchronous handler code.
Use promises to avoid the overhead of the try..catch
block or when using functions that return promises. For example:
app.get('/', function (req, res, next) {
Promise.resolve().then(function () {
throw new Error('BROKEN');
}).catch(next); // Errors will be passed to WebApp.
});
Since promises automatically catch both synchronous errors and rejected promises, you can simply provide next
as the final catch handler and WebApp
will catch errors, because the catch handler is given the error as the first argument.
Whichever method you use, if you want WebApp
error handlers to be called in and the application to survive, you must ensure that WebApp
receives the error.
The Default Error Handler
WebApp
comes with a built-in error handler that takes care of any errors that might be encountered in the app. This default error-handling middleware function is added at the end of the middleware function stack.
If you pass an error to next()
and you do not handle it in a custom error handler, it will be handled by the built-in error handler; the error message will be written to the client.
If you call next()
with an error after you have started writing the response (for example, if you encounter an error while streaming the response to the client) the WebApp
default error handler closes the connection and fails the request.
So when you add a custom error handler, you must delegate to the default WebApp
error handler, when the headers have already been sent to the client:
function errorHandler(err, req, res, next) {
var status = 404;
var reason = 'Not found';
if (err instanceof Error) {
status = typeof err.status === 'number' ? err.status : 500;
reason = err.message;
console.warn('finalHandle fail:', err.message);
}
if (!res.headersSent) {
res.sendStatus(status, reason);
} else {
req.close();
console.warn('finalHandle close request');
}
}
Note that the default error handler can get triggered if you call next()
with an error in your code more than once, even if custom error handling middleware is in place.
Writing Error Handlers
Define error-handling middleware functions in the same way as other middleware functions, except error-handling functions have four arguments instead of three: (err, req, res, next)
. For example:
app.use(function(err, req, res, next) {
if (res.headersSent) {
return next(err);
}
var status = err.status ? err.status : res.status();
status = status < 400 ? 500 : status;
res.status(status).json({status: status, msg: err.message});
});
You define error-handling middleware last, after other app.use()
and routes calls; for example:
var bodyParser = require('middleware').bodyParser;
app.use(bodyParser.urlencoded());
app.use(bodyParser.json());
app.use(function (err, req, res, next) {
// logic
});
Responses from within a middleware function can be in any format, such as an HTML error page, a simple message, or a JSON string.
For organizational (and higher-level framework) purposes, you can define several error-handling middleware functions, much as you would with regular middleware functions. For example, to define an error-handler for requests made by using XHR
and those without:
var bodyParser = require('middleware').bodyParser;
app.use(bodyParser.urlencoded());
app.use(bodyParser.json());
app.use(logErrors);
app.use(clientErrorHandler);
app.use(errorHandler);
In this example, the generic logErrors
might write request and error information to stderr
, for example:
function logErrors (err, req, res, next) {
console.error(err.stack);
next(err);
}
Also in this example, clientErrorHandler
is defined as follows; in this case, the error is explicitly passed along to the next one.
Notice that when not calling “next” in an error-handling function, you are responsible for writing (and ending) the response.
function clientErrorHandler(err, req, res, next) {
if (req.xhr) {
res.status(500).send({ error: 'Something failed!' });
} else {
next(err);
}
}
Implement the “catch-all” errorHandler
function as follows (for example):
function errorHandler (err, req, res, next) {
res.status(500);
res.render('error', { error: err });
}
Template Engines
A template engine enables you to use static template files in your application. At runtime, the template engine replaces variables in a template file with actual values, and transforms the template into an HTML file sent to the client. This approach makes it easier to design an HTML page.
The WebApp
uses ejs
EJS as its default template engines.
Using Template Engines
To render template files, set the following in you application before app.start()
:
views
, the directory where the template files are located. Eg:app.set('views', './views')
. default: 'views' directory in the application root directory.view engine
, the template engine to use. Eg.app.set('view engine', '.ejs')
. default:ejs
template engines.
After the view engine is set, you don’t have to specify the engine or load the template engine module in your app; WebApp
loads the module internally.
Create a ejs
template file named index.ejs
in the views
directory, with the following content:
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
</head>
<body>
<h1><%= message %></h1>
</body>
</html>
Then create a route to render the index.ejs
file.
app.get('/', function (req, res) {
res.render('index', { title: 'Hey', message: 'Hello world!' });
});
When you make a request to the home page, the index.ejs
file will be rendered as HTML.
Developing Template Engines
Use the app.engine(ext, callback)
method to create your own template engine. ext
refers to the file extension, and callback
is the template engine function, which accepts the following items as parameters: the location of the file, the options object, and the callback function.
The following code is an example of implementing a very simple template engine for rendering .te
files.
// Define the template engine
app.engine('.te', function (filePath, options, callback) {
var content = fs.readString(filePath);
if (!content) {
return callback(new Error('Read file error.'));
}
var rendered = content.replace('#title#', '<title>' + options.title + '</title>')
.replace('#message#', '<h1>' + options.message + '</h1>');
return callback(null, rendered);
});
app.set('views', './views'); // specify the views directory
app.set('view engine', '.te'); // register the template engine
Your app will now be able to render .te
files. Create a file named index.te
in the 'views'
directory with the following content.
#title#
#message#
Then, create the following route in your app.
app.get('/', function (req, res) {
res.render('index', { title: 'Hey', message: 'Hello World!' });
});
When you make a request to the home page, 'index.te'
will be rendered as HTML.
Using Middleware
Access Query Params
When you use the GET
method to send a request, you can include the query params in the URL. WebApp
has integrated queryParser
middleware. WebApp
will automatically parse the query parameter, just accessing the query parameter via the req.query
object.
The following example shows how to access query params.
// Handle request.
app.get('/query', function (req, res) {
var bodyCtx = `<p>id:${req.query.id}</p>
<p>id:${req.query.name}</p>`;
var ctx = `
<html>
<head>
<link rel="icon" href="data:image/ico;base64,aWNv">
</head>
<body>
<H1>req querys</H1>
${bodyCtx}
</body>
</html>`;
res.send(ctx);
});
You can send request include the query params :
http://<192.168.7.32>:8000/query?id=123&name=foo
Handle Form
The bodyParser
middleware parses the post data and saves the result in req.body
. The bodyParser
middleware can be used to parse the submitted form data.
The follow example shows how to handle a request's form. The bodyParser
middleware can also parse other post data. For more information, please refer to bodyParser.
var bodyParser = require('middleware').bodyParser;
// Form page.
app.get('/index.html', function(req, res) {
var ctx = `
<html>
<head>
<title>file upload</title>
<link rel="icon" href="data:image/ico;base64,aWNv">
</head>
<body>
<form action="/urlencoded" method="POST">
First Name: <input type="text" name="first_name"> <br>
Last Name: <input type="text" name="last_name"> <br>
pwd: <input type="password" name="pwd"> <br>
<input type="radio" name="sex" value="male"> male <br>
<input type="radio" name="sex" value="female">Female <br>
<input type="checkbox" name="vehicle" value="Bike">I have a bike<br>
<input type="submit" value="Submit">
</form>
</body>
</html>`;
res.send(ctx);
});
// Handle request form.
app.post('/urlencoded', bodyParser.urlencoded(), function(req, res){
var bodyCtx = '';
for (var k in req.body) {
bodyCtx += `<p>${k}: ${req.body[k]}</p>`;
}
var ctx = `
<html>
<head>
<link rel="icon" href="data:image/ico;base64,aWNv">
</head>
<body>
<H1>req body</H1>
${bodyCtx}
</body>
</html>`;
res.send(ctx);
});
Now, you can load the page to submit form.
http://<192.168.7.32>:8000/index.html
You can also set bodyParser.urlencoded()
as Independent routing middleware as follow,. It will parser all requests begin with root path.
app.use(bodyParser.urlencoded());
Serving Static Files
To serve static files such as images, CSS files, and JavaScript files, use the serveStatic
built-in middleware.
The serveStatic
middleware created:
var serveStatic = require('middleware').serveStatic;
serveStatic(root, options);
The root
argument specifies the root directory from which to serve static assets. For more information on the options
argument, see serveStatic.
For example, use the following code to serve images, CSS files, and JavaScript files in a directory named public
:
app.use(serveStatic('./public'));
Now, you can load the files that are in the public
directory.
http://<192.168.7.32>:8000/images/logo.gif
http://<192.168.7.32>:8000/css/style.css
http://<192.168.7.32>:8000/js/main.js
http://<192.168.7.32>:8000/hello.html
WebApp
looks up the files relative to the static directory, so the name of the static directory is not part of the URL.
To use multiple static assets directories, call the serveStatic
middleware function multiple times:
app.use(serveStatic('./public'));
app.use(serveStatic('./files'));
WebApp
looks up the files in the order in which you set the static directories with the serveStatic
middleware function.
To create a virtual path prefix (where the path does not actually exist in the file system) for files that are served by the serveStatic
function, specify a mount path for the static directory, as shown below:
app.use('/static', serveStatic('./public'));
Now, you can load the files that are in the public
directory from the /static
path prefix.
http://<192.168.7.32>:8000/static/images/logo.gif
http://<192.168.7.32>:8000/static/css/style.css
http://<192.168.7.32>:8000/static/js/main.js
http://<192.168.7.32>:8000/static/hello.html
Upload Files
To update files to server, use the multer
built-in middleware.
The multer
middleware created:
var multer = require('middleware').multer;
var upload = multer({ dest: './uploads', limits: {fileSize: 1 * 1024 * 1024}});
The dest
option specifies the root directory to which to save update files. limits
option specifies limit size of file. For more information on the options
argument, see multer.
For example, use the following code to save a file in a directory named uploads
. The uploaded file info can be access from req.file
object, such as originalname
, filename
, path
, size
.
app.post('/single', upload.single('file'), function (req, res, next) {
var bodyCtx = '';
for (var k in req.file) { // req.file is the 'file' file
bodyCtx += `<p>${k}: ${req.file[k]}</p>`;
}
var ctx = `
<html>
<head>
<link rel="icon" href="data:image/ico;base64,aWNv">
</head>
<body>
<H1>File info</H1>
${bodyCtx}
</body>
</html>`;
res.send(ctx);
});
The request form as follow, enctype="multipart/form-data"
is necessary.
<p>Update a single file(.*)</p>
<form action="/single" method="POST" enctype="multipart/form-data">
File: <input type="file" name="file"><br />
<input type="submit">
</form>
If you want to specify the upload file types, set the 'access' attribute.
File: <input type="file" name="file" accept=".jpg, .jpeg, .png, .gif"><br />
To upload multiple files, set multiple
attribute. The request page as follow.
<p>Update files(.jpg, .jpeg, .png, .gif)</p>
<form action="/mult" method="POST" enctype="multipart/form-data">
Files: <input type="file" name="photos" accept=".jpg, .jpeg, .png, .gif" multiple><br />
<input type="submit">
</form>
The follow code show how to handle multiple files once a time:
app.post('/mult', upload.array('photos', 4), function (req, res) {
// req.files is array of `photos` files
var bodyCtx = '';
for (var i in req.files) {
var file = req.files[i];
for (var k in file) {
bodyCtx += `<p>${k}: ${file[k]}</p>`;
}
bodyCtx += `<br/>`;
}
var ctx = `
<html>
<head>
<link rel="icon" href="data:image/ico;base64,aWNv">
</head>
<body>
<H1>Files info</H1>
${bodyCtx}
</body>
</html>`;
res.send(ctx);
});
In the above example, the middleware limits the maximum of 4 files that can be received.
Using Session
The http protocol is stateless, and sometimes we need to track the state of the application. The browser records session information by saving 'cookies'. 'session' is an enhanced session tracking technology implemented on the server side. and It provides better security.s
The session
middleware saves session information on the server and records the session information summary in 'cookie'.
Setting the session
middleware:
app.use(
session({
secret: "keyboard cat",
resave: false,
cookie: {
maxAge: 24 * 3600 * 1000,
},
store: new session.SqliteStore(),
})
);
The main options are explained below. For more options, please refer to session.
secret
: This is the secret used to sign the session ID cookie.resave
: Forces the session to be saved back to the session store, even if the session was never modified during the request. Otherwise set it tofalse
.cookie.maxAge
: Specifies thenumber
(in milliseconds) to use when calculating theExpires
Set-Cookie
attribute.store
: The session store instance.MemoryStore
andSqliteStore
are available in the middleware ofsession
.SqliteStore
storage is recommended in production environments.
The following example shows using session to record the number of times a page is viewed by each customer.
app.use(function (req, res, next) {
if (!req.session.views) {
req.session.views = {};
}
// get the url pathname
var pathname = req.url;
// count the views
req.session.views[pathname] = (req.session.views[pathname] || 0) + 1;
next();
});
To read session as follow:
// Show viewed times of a page.
app.get('/foo', function (req, res) {
var ctx = `
<html>
<head>
<link rel="icon" href="data:image/ico;base64,aWNv">
</head>
<body>
<p>you viewed this page(/foo) ${req.session.views['/foo']} times</p>
</body>
</html>`;
res.send(ctx);
});
Now you can load the page to observe how many times the page has been loaded.
http://<192.168.7.32>:8000/foo